package org.osomit.sacct.session.manager.impl;

import org.osomit.sacct.session.manager.iface.SessionManager;
import org.osomit.sacct.session.token.impl.SessionToken;
import org.osomit.sacct.session.token.provider.SessionTokenProvider;
import org.osomit.sacct.session.token.iface.Token;
import org.osomit.sacct.session.registry.iface.TokenRegistry;
import org.osomit.sacct.entity.transfer.SessionHandshakeResponse;
import org.osomit.sacct.entity.transfer.KeyExchangeResponse;
import org.osomit.sacct.security.key.exchange.impl.SkipServer;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.osomit.sacct.security.SecurityException;
import org.osomit.sacct.security.otp.iface.OTPToken;
import org.osomit.sacct.security.otp.impl.OTPTokenImpl;
import org.osomit.sacct.security.otp.impl.HOTPAlgorithm;
import org.osomit.sacct.server.config.iface.ServerConfig;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

import java.util.Collection;

/**
 * @author Jian Fang (John.Jian.Fang@gmail.com)
 *
 * Date: Jul 2, 2009
 */
@Singleton
public class SessionManagerImpl implements SessionManager {
    private static final String CANNOT_FIND_SESSION = "Cannot find session with id ";
    private static final String SESSION_REACHED_MAXIMUM = "Session number reaches the maximum ";
    private static final String SESSION_ALREADY_EXPIRED = "Session has already expired ";
    private static final String REMOVE_EXPIRED_SESSION = "Remove expired session ";

    private Log log = LogFactory.getLog(SessionManagerImpl.class);

    private SkipServer skipServer = new SkipServer();

    private SessionTokenProvider provider;

    private TokenRegistry registry;

    private ServerConfig config;

    public ServerConfig getConfig() {
        return config;
    }

    @Inject
    public void setConfig(ServerConfig config) {
        this.config = config;
    }

    public TokenRegistry getRegistry() {
        return registry;
    }

    @Inject
    public void setRegistry(TokenRegistry registry) {
        this.registry = registry;
    }

    public SessionTokenProvider getProvider() {
        return provider;
    }

    @Inject
    public void setProvider(SessionTokenProvider provider) {
        this.provider = provider;
    }

    protected void checkSessionRegistry(){
        int num = this.registry.getTokenNumber();
        if(num > this.config.getNumToCleanSession()){
            Collection<Token> tokens = this.registry.getAllTokens();
            for(Token token: tokens){
                if(token.isExpired()){
                    log.info(REMOVE_EXPIRED_SESSION + token.getId());
                    this.registry.deleteToken(token.getId());
                }
            }
        }
    }

    //Use hard-coded OTP token parameters for the time being because too many configurations will
    //make people confused and they do not really need to know how to adjust the parameters for
    //the One Time Password Token
    protected OTPToken generateOTPToken(String sn, byte[] sharedSecret){

        return new OTPTokenImpl(sn, sharedSecret, 1000, this.getBais(sn), 9, true, 20, 100, new HOTPAlgorithm());
    }

    protected long getBais(String sn){
        String[] parts = sn.split("-");

        return Long.parseLong(parts[1]);
    }
    
    public SessionHandshakeResponse handshake(byte[] clientPubKeyEnc) {
        KeyExchangeResponse resp = this.skipServer.agree(clientPubKeyEnc);
        SessionToken token = provider.get();
        token.setSessionKey(resp.getKey());
        if(this.config.isUseOTP()){
            OTPToken otptk = this.generateOTPToken(token.getId(), token.getSessionKey().getEncoded());
            token.setOtpToken(otptk);
        }
        this.checkSessionRegistry();
        if (this.registry.getTokenNumber() < this.config.getMaxSessionNum()) {
            registry.addToken(token);
            SessionHandshakeResponse shresp = new SessionHandshakeResponse();
            shresp.setServerPubKeyEnc(resp.getPubKeyEnc());
            shresp.setToken(token);

            return shresp;
        } else {
            log.error(SESSION_REACHED_MAXIMUM + this.config.getMaxSessionNum());
            throw new SecurityException(SESSION_REACHED_MAXIMUM + this.config.getMaxSessionNum());
        }
    }

    public void updateToken(SessionToken token) {
        Token tk = this.registry.getToken(token.getId());

        if (tk == null) {
            log.error(CANNOT_FIND_SESSION + token.getId());
            throw new SecurityException(CANNOT_FIND_SESSION + token.getId());
        }
        
        if (token.isExpired()) {
            log.error(SESSION_ALREADY_EXPIRED + token.getId());
            this.registry.deleteToken(token.getId());
            log.info(REMOVE_EXPIRED_SESSION + token.getId());
            throw new SecurityException(SESSION_ALREADY_EXPIRED + token.getId());
        }

        this.registry.updateToken(token);
    }

    public SessionToken getToken(String id) {
        SessionToken token = (SessionToken) this.registry.getToken(id);

        if(token == null){
            log.error(CANNOT_FIND_SESSION + id);
            throw new SecurityException(CANNOT_FIND_SESSION + id);
        }

        if (token.isExpired()) {
            log.error(SESSION_ALREADY_EXPIRED + token.getId());
            this.registry.deleteToken(token.getId());
            log.info(REMOVE_EXPIRED_SESSION + token.getId());
            throw new SecurityException(SESSION_ALREADY_EXPIRED + token.getId());
        }

        return token;
    }

    public int getTokenNumber() {
        return this.registry.getTokenNumber();
    }

    public void clear() {
        this.registry.clear();
    }

    public boolean isUseOTP() {
        return this.config.isUseOTP();
    }
}
